﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;

namespace Kokomo.Environment
{
	/// <summary>
	/// Represents a file type.
	/// </summary>
	/// <remarks>
	/// The environment uses <see cref="FileType"/>s to associate files
	/// with editors, and is defined by <see cref="Masks"/>.
	/// See <see cref="Matches"/> for implementation details.
	/// </remarks>
	/// <example>
	/// <code>
	/// FileType xmlType = new FileType(&quot;XML document&quot;, new string[] { &quot;*.xml&quot; });
	/// </code>
	/// </example>
	public class FileType
	{
		static FileType()
		{
			Type patternMatcherType = typeof(FileSystemWatcher).Assembly.GetType("System.IO.PatternMatcher");
			MethodInfo patternMatchMethod = patternMatcherType.GetMethod("StrictMatchPattern", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
			PatternComparerDelegate = patternMatchMethod.CreateDelegate<Func<string, string, bool>>();
		}

		private static Func<string, string, bool> PatternComparerDelegate;

		/// <summary>
		/// Initializes a new instance of <see cref="FileType"/>.
		/// </summary>
		/// <param name="description">The description of the file type.</param>
		/// <param name="masks">The filename masks that define this file type.</param>
		/// <param name="defaultExtension">The default extension to use for new files of this type.</param>
		/// <exception cref="ArgumentNullException"><paramref name="description"/> is <c>null</c>.</exception>
		/// <exception cref="ArgumentNullException"><paramref name="masks"/> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException"><paramref name="masks"/> contains one or more <c>null</c> elements.</exception>
		public FileType(
			UiString description,
			IEnumerable<string> masks,
			string defaultExtension = null
			)
		{
			if (description == null) throw new ArgumentNullException("description");
			if (masks == null) throw new ArgumentNullException("masks");

			string[] maskArray = masks.ToArray();
			if (maskArray.Length == 0) throw new ArgumentException("masks must contain at least one item");
			if (Array.IndexOf(maskArray, null) >= 0) throw new ArgumentException("Mask array cannot contain null values");

			this.Description = description;
			this.DefaultExtension = defaultExtension;
			this.Masks = maskArray;
		}

		public override string ToString()
		{
			return this.Description + "(" + string.Join("; ", this.Masks) + ")";
		}

		/// <summary>
		/// Gets the description of the file type.
		/// </summary>
		public UiString Description { get; private set; }
		/// <summary>
		/// Gets the filename masks that define this file type
		/// </summary>
		public IEnumerable<string> Masks { get; private set; }
		/// <summary>
		/// Gets the default extension to use for new files of this type.
		/// </summary>
		public string DefaultExtension { get; private set; }

		/// <summary>
		/// Determines whether a file name matches this file type.
		/// </summary>
		/// <param name="fileName">The file name to test.</param>
		/// <returns><c>true</c> if the file name matches; otherwise, <c>false</c>.</returns>
		/// <exception cref="ArgumentNullException"><paramref name="fileName"/> is <c>null</c>.</exception>
		/// <remarks>
		/// <paramref name="fileName"/> may either be a full path or just a file name,
		/// and doesn't need to actually exist, as the file is not accessed.
		/// Internally, the implementation uses <see cref="P:System.IO.PatternMatcher.StrictMatchPattern"/>,
		/// which is also used by <see cref="Directory.GetFiles(string,string)"/>.
		/// </remarks>
		public bool Matches(string fileName)
		{
			if (fileName == null) throw new ArgumentNullException("fileName");

			bool matches = false;
			foreach (var mask in this.Masks)
			{
				matches = PatternComparerDelegate(mask, fileName);
				if (matches) return true;
			}

			return false;
		}
	}
}
